#include "main.h"
#include "system_sm.h"
#include "sh_lib.h"
#include "sh_isr.h"
#include "sh_sm.h"
#include "rotary_encoder.h"
#include "digital_tube.h"
#include "board_config.h"
#include "board_relay.h"
#include "lift_info.h"


/* 系统状态机相关定义 */
enum sys_sm_state_e {
    SYS_SM_STATE_CONFIG = 1,
    SYS_SM_STATE_MENU,
    SYS_SM_STATE_RUNNING,
    SYS_SM_STATE_PASSWORD,
    SYS_SM_STATE_HARDWARE_TEST,
};

static sh_sm_t *sys_sm = NULL;

static sh_timer_t *hardware_test_timer = NULL;

static void system_sm_set_config_value(uint8_t item, uint16_t value);

bool is_hardware_testing = false;

/* 运行状态 */
static void sys_sm_state_running_cb(const sh_event_msg_t *e)
{
    switch (e->id) {
    case EVENT_BUTTON_OK:
        sh_sm_trans_to(sys_sm, SYS_SM_STATE_PASSWORD);
        rotary_encoder_exti_irq_ctrl(ENABLE);
        digital_tube_set_value(DIGITAL_TUBE_NUMBER, 0);
        board_config_password_adjust(0);
        break;

    // TODO: 正式版本要去除
    case EVENT_BUTTON_BACK:
        __disable_irq();
        HAL_NVIC_SystemReset();
        break;

    default:
        break;
    }
}

static uint32_t hardware_test_cnt = 0;

static void hardware_test_overtick_cb(void* param)
{
    if (hardware_test_cnt++ % 2 == 0) {
        digital_tube_set_value(DIGITAL_TUBE_CLEAR, 0);
        HAL_GPIO_WritePin(GPIOB, Y1_Pin|Y2_Pin|Y3_Pin|Y4_Pin|Y5_Pin|Y6_Pin|Y7_Pin|Y8_Pin|Y9_Pin|Y10_Pin|Y11_Pin|Y12_Pin, GPIO_PIN_RESET);
        if (lift.oc_flag.oc_flag.sensor_disconn == 0) {
            HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
        }
    } else {
        digital_tube_set_value(DIGITAL_TUBE_NUMBER, 888);
        HAL_GPIO_WritePin(GPIOB, Y1_Pin|Y2_Pin|Y3_Pin|Y4_Pin|Y5_Pin|Y6_Pin|Y7_Pin|Y8_Pin|Y9_Pin|Y10_Pin|Y11_Pin|Y12_Pin|LED_Pin, GPIO_PIN_SET);
    }
}

/* 硬件测试状态 */
static void sys_sm_state_hardware_test_cb(const sh_event_msg_t *e)
{
    switch (e->id) {
    case EVENT_BUTTON_BACK:
        __disable_irq();
        HAL_NVIC_SystemReset();
        break;

    default:
        break;
    }
}

/* 密码状态 */
static void sys_sm_state_password_cb(const sh_event_msg_t *e)
{
    uint16_t temp = 0;

    switch (e->id) {
    case EVENT_BUTTON_OK:
        temp = digital_tube_get_num_value();
        if (temp == lift.password) {
            sh_sm_trans_to(sys_sm, SYS_SM_STATE_MENU);
            digital_tube_set_value(DIGITAL_TUBE_MENU, 0);
        } else if (temp == lift.hardware_test) {
            is_hardware_testing = true;
            hardware_test_cnt = 0;
            digital_tube_set_value(DIGITAL_TUBE_NUMBER, 888);
            HAL_GPIO_WritePin(GPIOB, Y1_Pin|Y2_Pin|Y3_Pin|Y4_Pin|Y5_Pin|Y6_Pin|Y7_Pin|Y8_Pin|Y9_Pin|Y10_Pin|Y11_Pin|Y12_Pin|LED_Pin, GPIO_PIN_SET);
            hardware_test_timer = sh_sm_start_normal_timer(sys_sm, 1000, hardware_test_overtick_cb);
            sh_sm_trans_to(sys_sm, SYS_SM_STATE_HARDWARE_TEST);
        } else {
            digital_tube_set_value(DIGITAL_TUBE_NUMBER, 0);
            board_config_password_adjust(0);
        }
        break;

    case EVENT_BUTTON_BACK:
        rotary_encoder_exti_irq_ctrl(DISABLE);
        digital_tube_set_value(DIGITAL_TUBE_RUNNING, 1);
        sh_sm_trans_to(sys_sm, SYS_SM_STATE_RUNNING);
        break;

    case EVENT_BUTTON_UP:
        temp = board_config_password_adjust(e->size);
        digital_tube_set_value(DIGITAL_TUBE_NUMBER, temp);
        break;

    case EVENT_BUTTON_DOWN:
        temp = board_config_password_adjust(-e->size);
        digital_tube_set_value(DIGITAL_TUBE_NUMBER, temp);
        break;

    default:
        break;
    }
}


/* 菜单状态 */
static void sys_sm_state_menu_cb(const sh_event_msg_t *e)
{
    switch (e->id) {
    case EVENT_BUTTON_OK:
        sh_sm_trans_to(sys_sm, SYS_SM_STATE_CONFIG);
        uint8_t item = digital_tube_menu_get_index();
        uint16_t num = board_config_adjust(item, 0);
        if (item == CONFIG_SPECIFIED_SPEED) {
            digital_tube_set_value(DIGITAL_TUBE_SPEED, num);
        } else {
            digital_tube_set_value(DIGITAL_TUBE_NUMBER, num);
        }
        break;

    case EVENT_BUTTON_UP:
        digital_tube_menu_adjust_value(1);
        break;

    case EVENT_BUTTON_DOWN:
        digital_tube_menu_adjust_value(-1);
        break;

    case EVENT_BUTTON_BACK:
        rotary_encoder_exti_irq_ctrl(DISABLE);
        digital_tube_set_value(DIGITAL_TUBE_RUNNING, 1);
        sh_sm_trans_to(sys_sm, SYS_SM_STATE_RUNNING);
        break;

    default:
        break;
    }
}


/* 参数设置状态 */
static void sys_sm_state_config_cb(const sh_event_msg_t *e)
{
    uint8_t item = digital_tube_menu_get_index();
    uint16_t temp = 0;

    switch (e->id) {
    case EVENT_BUTTON_OK:
        temp = digital_tube_get_num_value();
        system_sm_set_config_value(item, temp);
    case EVENT_BUTTON_BACK:
        sh_sm_trans_to(sys_sm, SYS_SM_STATE_MENU);
        digital_tube_set_type(DIGITAL_TUBE_MENU);
        break;

    case EVENT_BUTTON_UP:
        if (item == 10 || item == 11) {
            break;
        }
        temp = board_config_adjust(item, e->size);
        if (item == CONFIG_SPECIFIED_SPEED) {
            digital_tube_set_value(DIGITAL_TUBE_SPEED, temp);
        } else {
            digital_tube_set_value(DIGITAL_TUBE_NUMBER, temp);
        }
        break;

    case EVENT_BUTTON_DOWN:
        if (item == 10 || item == 11) {
            break;
        }
        temp = board_config_adjust(item, -e->size);
        if (item == CONFIG_SPECIFIED_SPEED) {
            digital_tube_set_value(DIGITAL_TUBE_SPEED, temp);
        } else {
            digital_tube_set_value(DIGITAL_TUBE_NUMBER, temp);
        }
        break;

    default:
        break;
    }
}

static void led_timer_cb(void *param)
{
    if (is_hardware_testing) {
        return;
    }

    if (lift.oc_flag.oc_flag.sensor_disconn) {
        HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
        return;
    }

    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}

void system_sm_init(void)
{
    uint8_t events[] = {EVENT_BUTTON_OK, EVENT_BUTTON_UP, EVENT_BUTTON_DOWN, EVENT_BUTTON_BACK};

    sys_sm = sh_sm_create(SH_GROUP(events), HAL_GetTick);
    
    sh_sm_state_create(sys_sm, SYS_SM_STATE_RUNNING);
    sh_sm_state_create(sys_sm, SYS_SM_STATE_CONFIG);
    sh_sm_state_create(sys_sm, SYS_SM_STATE_MENU);
    sh_sm_state_create(sys_sm, SYS_SM_STATE_PASSWORD);
    sh_sm_state_create(sys_sm, SYS_SM_STATE_HARDWARE_TEST);

    uint8_t running_events[] = {EVENT_BUTTON_OK, EVENT_BUTTON_BACK};
    sh_sm_state_subscribe_events(sys_sm, SYS_SM_STATE_RUNNING, SH_GROUP(running_events), sys_sm_state_running_cb);

    uint8_t setting_events[] = {EVENT_BUTTON_OK, EVENT_BUTTON_UP, EVENT_BUTTON_DOWN, EVENT_BUTTON_BACK};
    sh_sm_state_subscribe_events(sys_sm, SYS_SM_STATE_CONFIG,  SH_GROUP(setting_events), sys_sm_state_config_cb);
    sh_sm_state_subscribe_events(sys_sm, SYS_SM_STATE_MENU,    SH_GROUP(setting_events), sys_sm_state_menu_cb);
    sh_sm_state_subscribe_events(sys_sm, SYS_SM_STATE_PASSWORD,SH_GROUP(setting_events), sys_sm_state_password_cb);

    sh_sm_state_subscribe_event(sys_sm, SYS_SM_STATE_HARDWARE_TEST, EVENT_BUTTON_BACK, sys_sm_state_hardware_test_cb);

    sh_sm_trans_to(sys_sm, SYS_SM_STATE_RUNNING);

    digital_tube_set_value(DIGITAL_TUBE_RUNNING, 1);

    sh_sm_start_normal_timer(sys_sm, 2, digital_tube_display_timer_cb);
    sh_sm_start_normal_timer(sys_sm, 1, rotary_encoder_button_timer_cb);
    sh_sm_start_normal_timer(sys_sm, 1000, led_timer_cb);

    extern int rt_hw_interrupt_disable(void);
    extern void rt_hw_interrupt_enable(int);

    sh_isr_t isr = {
        .disable = rt_hw_interrupt_disable,
        .enable  = rt_hw_interrupt_enable,
    };
    sh_isr_register(&isr);
}

void system_sm_handler(void)
{
    sh_sm_handler(sys_sm);
}

void system_sm_send_event(uint8_t event_id)
{
    if (!sys_sm) {
        return;
    }
    
    sh_sm_publish_event(sys_sm, event_id);
}

void system_sm_send_event_with_param(uint8_t event_id, uint32_t param)
{
    if (!sys_sm) {
        return;
    }

    sh_sm_publish_event_with_param(sys_sm, event_id, param);
}


static void system_sm_set_config_value(uint8_t item, uint16_t value)
{
    uint32_t position = 0;

    if (item < CONFIG_COVER_ENABLE) {
        board_config_write(item, value);
    }

    switch (item) {
    case CONFIG_SPECIFIED_SPEED:
        lift.specified_speed = value;
        break;

    case CONFIG_TOTAL_FLOOR:
        lift_ctrl_reset_total_floor(value);
        board_relay_slow_down_close();
        board_relay_flat_floor_close();
        break;

    case CONFIG_LEARNING_OF_FLOOR_HEIGHT:
        if (lift_info_get_position(&position) == 0) {
            lift_ctrl_set_flat_pos(value - 1, position);
        }
        break;

    case CONFIG_TOP_FORCE_POS:
        lift.up_slow_down_pos = value;
        for (int i = 0; i < lift.total_floor; i++) {
            lift.floor_info[i].up_slow_down_pos = lift.floor_info[i].flat_pos + lift.up_slow_down_pos;
        }
        break;

    case CONFIG_BUTTOM_FORCE_POS:
        lift.down_slow_down_pos = value;
        for (int i = 0; i < lift.total_floor; i++) {
            lift.floor_info[i].down_slow_down_pos = lift.floor_info[i].flat_pos - lift.down_slow_down_pos;
        }
        break;

    case CONFIG_TOP_FLAT_LAYER:
        lift.up_flat_pos = value;
        for (int i = 0; i < lift.total_floor; i++) {
            lift.floor_info[i].up_flat_pos = lift.floor_info[i].flat_pos + lift.up_flat_pos;
            lift.floor_info[i].up_flat_pos += ((int)lift.floor_info[i].up_flat_trimming_pos - 50);
        }
        break;

    case CONFIG_BUTTOM_FLAT_LAYER:
        lift.down_flat_pos = value;
        for (int i = 0; i < lift.total_floor; i++) {
            lift.floor_info[i].down_flat_pos = lift.floor_info[i].flat_pos - lift.down_flat_pos;
            lift.floor_info[i].down_flat_pos += ((int)lift.floor_info[i].down_flat_trimming_pos - 50);
        }
        break;

    case CONFIG_TOP_TRIMMING_POS:
        lift_ctrl_set_up_flat_trimming_pos(lift.current_floor, value);
        break;

    case CONFIG_DOWN_TRIMMING_POS:
        lift_ctrl_set_down_flat_trimming_pos(lift.current_floor, value);
        break;

    // TODO: 覆盖使能需要作用几次？？？
    case CONFIG_COVER_ENABLE:
        lift.is_cover_enable = value;
        board_config_set_item_value(CONFIG_COVER_ENABLE, value);
        break;

    default:
        break;
    }
}